﻿using System;
using System.Net;
using System.Threading;
using AgentListener.Controllers;
using Common.Configuration;
using Common.Annotations;
using NLog;

namespace AgentListener.DataProcessing
{
    /// <summary> Слушает HTTP-запросы и передает их необходимому обработчику </summary>
    public class Listener : IListener
    {
        private readonly IListenerConfig _config;
        private readonly HttpListener _httpListener;
        private bool _working;
        private readonly IContextProcessor _contextProcessor;
        private Thread _workerThread;
        private int _requestCount;
        private static readonly Logger NLog = LogManager.GetCurrentClassLogger();

        public Listener([NotNull] IListenerConfig config, IContextProcessor contextProcessor)
        {
            if (config == null) throw new ArgumentNullException("config");

            _httpListener = new HttpListener();
            _contextProcessor = contextProcessor;

            _config = config;

            try
            {
                foreach (var prefix in _config.Prefixes)
                    _httpListener.Prefixes.Add(prefix);
            }
            catch (Exception e)
            {
                NLog.Fatal(e);
                throw new ListenerException(e);
            }
        }

        public void Start()
        {
            _contextProcessor.Start();
            _httpListener.Start();
            _working = true;
            NLog.Trace("Listener started");

            _requestCount = 0;
            _workerThread = _workerThread ?? new Thread(MainLoop);
            _workerThread.Start();
            NLog.Trace("Main listener thread started");
        }

        public void Stop()
        {
            _working = false;
            try
            {
                _httpListener.Stop();
                _httpListener.Close();
            }
            catch (Exception e)
            {
                NLog.Fatal(e);
                throw new ListenerException(e);
            }
            finally
            {
                _contextProcessor.Stop();
            }
        }

        /// <summary>
        /// Постоянно создает новые вызовы на получение запроса
        /// </summary>
        private void MainLoop()
        {
            while (_working)
            {
                Thread.Sleep(10);
                if (_requestCount < _config.MaximumConcurentRequests)
                {
                    try
                    {
                        _httpListener.BeginGetContext(OnGetContext, _httpListener);
                        Interlocked.Increment(ref _requestCount);
                    }
                    catch (Exception e)
                    {
                        NLog.Error(e);
                    }
                }
            }
        }

        private void OnGetContext(IAsyncResult ar)
        {
            try
            {
                var context = _httpListener.EndGetContext(ar);
                Interlocked.Decrement(ref _requestCount);

                NLog.Trace("Got data from {0}", context.Request.RemoteEndPoint);
                _contextProcessor.Add(new HttpDataHolder(context));
            }
            catch (ObjectDisposedException e)
            {
                NLog.Error(e);
                //throw new ListenerException(e);
            }
        }
    }

    public class ListenerException : Exception
    {
        public ListenerException([NotNull] Exception exception)
            : base("Необработанная ошибка", exception)
        {
            if (exception == null) throw new ArgumentNullException("exception");
        }
    }
}